<?php

namespace pmvc;

use pmvc\log\Log;
use pmvc\io\IOUtil;
use pmvc\ClassLoader;
use Exception;
use DirectoryIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

/**
 * Abstract implementation of the {@link ClassLoader}
 * interface that uses file operations like, require,
 * require_once, file_exists, etc. to load classes.
 *
 */
abstract class AbstractClassLoader
	implements ClassLoader {

	/**
	 * Function that must return an array of
	 * classpath elements (strings).
	 * @return Array array of classpath elements
	 */
	protected abstract function getClasspath();

	/**
	 * Returns the separator of paths.
	 * @return path separator character
	 */
	protected function getPathSeparator() {
		return DIRECTORY_SEPARATOR;
	}

	/**
	 * Indicates whether or not the class loader should
	 * use {@link realpath} on paths before using {@link require}
	 * and {@link requre_once}.
	 * @return boolean
	 */
	protected function shouldUseRealpath() {
		return true;
	}

	/**
	 * Indicates whether or not the given file
	 * exists according to the extending
	 * classloader.
	 * @return boolean
	 */
	protected function fileExists($file) {
		return file_exists($file);
	}

	/**
	 * Normalizes path names for the ClassLoader.
	 * @param string $file
	 * @return string the normalized path name
	 */
	protected function normalizePath($file) {
		$sep = $this->getPathSeparator();
		$file = str_replace('\\', $sep, $file);
		if (!substr($file, 0, 1)!=$sep) {
			$file = $sep.$file;
		}
		return $file;
	}

	/**
	 * {@inheritDoc}
	 */
	public function requireFile($file, $once=false) {
		$file = $this->getResourcePath($file);
		if ($file===false) { return false; }
		if ($once) {
			require_once($file);
		} else {
			require($file);
		}
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function loadClass($className) {

		// bail early if possible
		if (class_exists($className, false)) {
			// for whatever reason, this happens sometimes
			return true;
		}

		// parse namespace and classname
		$sep = $this->getPathSeparator();
		$className = ltrim($className, "\\");
		$namespace = '';
		if ($pos=strripos($className, "\\")) {
			$namespace = str_replace("\\", $sep, substr($className, 0, $pos)).$sep;
			$className = substr($className, $pos+1);
		}
		
		// ns/package/Class.class.php
		$filename = $this->getResourcePath($namespace.$className.'.class.php');
		if ($filename!==false) {
			require_once($filename);
			return true;
		}
		
		// ns/package/Class.php
		$filename = $this->getResourcePath($namespace.$className.'.php');
		if ($filename!==false) {
			require_once($filename);
			return true;
		}
		
		// ns/package/A_Class.class.php
		$filename = $this->getResourcePath($namespace.str_replace('_', $sep, $className).'.class.php');
		if ($filename!==false) {
			require_once($filename);
			return true;
		}
		
		// ns/package/A_Class.php
		$filename = $this->getResourcePath($namespace.str_replace('_', $sep, $className).'.php');
		if ($filename!==false) {
			require_once($filename);
			return true;
		}

		// didn't work
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public function getResourcePaths($file, $recursive=true) {
		$file = $this->normalizePath($file);
		$ret = Array();
		$sep = $this->getPathSeparator();
	    foreach ($this->getClasspath() as $dir) {
	    	try {
				$itr = ($recursive)
					? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir.$file))
					: new DirectoryIterator($dir.$file);
				foreach ($itr as $info) {
					if (!$info->isDir()) {
						$ret []= $info->getPath().$sep.$info->getFilename();
					}
				}
	    	} catch(Exception $e) {
	    		// eat this, it's just an invalid path
	    		// in the classpath
	    	}
	    }
		return $ret;
	}

	/**
	 * {@inheritDoc}
	 */
	public function getResourcePath($file) {
		$file = $this->normalizePath($file);
	    foreach ($this->getClasspath() as $dir) {
	    	$f = $dir.$file;
	        if ($this->fileExists($f)===true) {
	        	return $f;
	        }
	    }
	    return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public function getResourceAsStream($file) {
		$file = $this->getResourcePath($file);
		if ($file===false) { return false; }
		return IOUtil::openFile($file, 'f');
	}

}

?>